/*
 -- IOTA Crypto Core
 --
 -- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
 -- discord: pmaxuw#8292
 -- https://gitlab.com/iccfpga-rv
 --
 -- Permission is hereby granted, free of charge, to any person obtaining
 -- a copy of this software and associated documentation files (the
 -- "Software"), to deal in the Software without restriction, including
 -- without limitation the rights to use, copy, modify, merge, publish,
 -- distribute, sublicense, and/or sell copies of the Software, and to
 -- permit persons to whom the Software is furnished to do so, subject to
 -- the following conditions:
 --
 -- The above copyright notice and this permission notice shall be
 -- included in all copies or substantial portions of the Software.
 --
 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 -- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 -- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
 */

#include "main.h"

#include "riscv.h"
#include "riscv-arch/arch-defines.h"

#include "micro-os-plus/diag/trace.h"
#include "debugprintf.h"

#include "Timer.h"

#include "gpio/gpio.h"
#include "gpio/secure_gpio.h"
/*
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
*/

extern Timer timer;

#define TEST_ASSERT_EQUAL(a,b)	(assert(a == b))

// needed for privileged calls by trap-handler
// has to be in protected read/writable memory
uint32_t MMDATA old_mepc = 0x00000000;
uint32_t MMDATA old_sp = 0x00000000;


void throw_exception(const char *expression, const char *file, int line) {
	debugPrintf("%s %s %d\n", expression, file, line);
}

void system_halt() {
	while (1) {
		GPIO::setBit(GPIO_LED_USER);
		timer.sleep(1000);
		GPIO::clrBit(GPIO_LED_USER);
		timer.sleep(1000);
	}
}

// dummy to avoid linker errors
extern "C" void os_terminate(int i) {
}

// trap handler
extern "C" void redirectTrap() {
	printf("unhandled trap ... system hold\n");
	while (1)
		;
}

extern "C" void riscv_trap_entry();
extern "C" void SysTick_Handler(void);

extern "C" MMTEXT void riscv_core_handle_trap(void) {
	volatile int32_t cause = csr_read(mcause);
	volatile uint32_t mepc = csr_read(mepc);

	if (cause < 0) { //interrupt
		switch (cause & 0xFF) {
		case 5:
		case CAUSE_MACHINE_TIMER: {
			SysTick_Handler();
			break;
		}
		default:
			printf("cause %d\n", cause);
			redirectTrap();
			break;
		}
	} else { //exception
		switch (cause) {
		case CAUSE_ILLEGAL_INSTRUCTION: {
//			uint32_t mepc = csr_read(mepc);
//			uint32_t mstatus = csr_read(mstatus);
			break;
		}

		case CAUSE_UCALL:				// ecall from user-mode
			csr_write(mepc, mepc + 4);	// skip ecall-instruction
			break;
		case CAUSE_SCALL:
			break;
/*
		case 15:
			page_fault = 1;
			csr_write(mepc, mepc + 4);
			break;
*/
		default:

			debugPrintf("cause %d %08x\n", cause, mepc);
			redirectTrap();
			break;
		}
	}

}

// read out 64bit mcycle counter
// tries to detect overflows
uint64_t getMCycle() {
	uint32_t mcycleHi[2];
	uint32_t mcycleLo;

	mcycleHi[0] = csr_read(mcycleh);
	while (1) {
		mcycleLo = csr_read(mcycle);
		mcycleHi[1] = csr_read(mcycleh);
		// no overflow - break loop
		if (mcycleHi[0] == mcycleHi[1]) {
			break;
		}
		mcycleHi[0] = mcycleHi[1];
	}
	return (uint64_t) mcycleHi[0] << 32ull | (uint64_t) mcycleLo;
}


void cpu_startInterrupts() {
	csr_clear(mie, MSTATUS_MIE);
	csr_clear(mie, MIE_MTIE);

// always at 0x00000020
// mtvec not writable but removing this write also removes ISR
	uint32_t addrTrapEntry = (uint32_t) &riscv_trap_entry;
	csr_write(mtvec, addrTrapEntry);

	csr_set(mie, MIP_MTIP);
	csr_set(mip, MIP_MTIP);
	csr_write(mstatus, MSTATUS_MIE);
}

// restart FPGA reconfiguration
// veraltet
void MMTEXT reconfigure() {
	uint32_t* ptr = (uint32_t*) (0xf10a0000 + 0x124);
	*ptr = 0x1;	// reset fpga
}

